package org.geektimes.web.mvc;


import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.geektimes.di.ioc.Container;
import org.geektimes.web.mvc.controller.Controller;
import org.geektimes.web.mvc.controller.PageController;
import org.geektimes.web.mvc.controller.RestController;
import org.geektimes.web.validator.bean.validation.DelegatingValidator;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.substringAfter;

public class FrontControllerServlet extends HttpServlet {
    public static final String ValidatorBean = "bean/Validator";
    /**
     * 请求路径和 Controller 的映射关系缓存
     */
    private Map<String, Controller> controllersMapping = new HashMap<>();

    /**
     * 请求路径和 {@link HandlerMethodInfo} 映射关系缓存
     */
    private Map<String, HandlerMethodInfo> handleMethodInfoMapping = new HashMap<>();

    private Container parentContainer;
    /**
     * 初始化 Servlet
     *
     * @param servletConfig
     */
    @Override
    public void init(ServletConfig servletConfig) {

        ServletContext servletContext = servletConfig.getServletContext();
        //第一种获取config方法
        Config config = ConfigProviderResolver.instance().getConfig(servletContext.getClassLoader());
        //第一种获取config方法
        Config config1 =(Config) servletContext.getAttribute(ServletContext.class.getName());
        Container container =(Container) servletContext.getAttribute(Container.class.getName());
//        servletConfig.getServletContext().getAttribute();
        this.parentContainer = container;
        initHandleMethods();
    }

    /**
     * 读取所有的 RestController 的注解元信息 @Path
     * 利用 ServiceLoader 技术（Java SPI）
     */
    private void initHandleMethods() {
        try {
            for (Controller controller : ServiceLoader.load(Controller.class)) {
                Class<?> controllerClass = controller.getClass();

                //注入controller的依赖 注意此处是getDeclaredFields
                Field[] fields = controllerClass.getDeclaredFields();
                for (Field field : fields) {
                    int modifiers = field.getModifiers();
                    if (Modifier.isStatic(modifiers) || field.isAnnotationPresent(Resource.class)) {
                        String name = field.getAnnotation(Resource.class).name();
                        Object object = this.parentContainer.getObject(name);
                        field.setAccessible(true);
                        field.set(controller, object);
                    }
                }

                Path pathFromClass = controllerClass.getAnnotation(Path.class);
                String requestPath = pathFromClass.value();
                Method[] publicMethods = controllerClass.getDeclaredMethods();
                // 处理方法支持的 HTTP 方法集合
                // TODO 过滤方法，不能是所以方法都加进去，因为后面在绑定参数和校验是会报错
                for (Method method : publicMethods) {
                    Set<String> supportedHttpMethods = findSupportedHttpMethods(method);
                    Path pathFromMethod = method.getAnnotation(Path.class);
                    if (pathFromMethod != null) {
                        requestPath += pathFromMethod.value();
                    }
                    handleMethodInfoMapping.put(requestPath,
                            new HandlerMethodInfo(requestPath, method, supportedHttpMethods));
                }
                controllersMapping.put(requestPath, controller);
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取处理方法中标注的 HTTP方法集合
     *
     * @param method 处理方法
     * @return
     */
    private Set<String> findSupportedHttpMethods(Method method) {
        Set<String> supportedHttpMethods = new LinkedHashSet<>();
        for (Annotation annotationFromMethod : method.getAnnotations()) {
            HttpMethod httpMethod = annotationFromMethod.annotationType().getAnnotation(HttpMethod.class);
            if (httpMethod != null) {
                supportedHttpMethods.add(httpMethod.value());
            }
        }

        if (supportedHttpMethods.isEmpty()) {
            supportedHttpMethods.addAll(asList(HttpMethod.GET, HttpMethod.POST,
                    HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS));
        }

        return supportedHttpMethods;
    }

    /**
     * SCWCD
     *
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 建立映射关系
        // requestURI = /a/hello/world
        ThreadLocal<Config> tl =(ThreadLocal<Config>) request.getAttribute(ServletRequest.class.getName());
        Config config = tl.get();

        String requestURI = request.getRequestURI();
        // contextPath  = /a or "/" or ""
        String servletContextPath = request.getContextPath();
        String prefixPath = servletContextPath;
        // 映射路径（子路径）
        String requestMappingPath = substringAfter(requestURI,
                StringUtils.replace(prefixPath, "//", "/"));
        // 映射到 Controller
        Controller controller = controllersMapping.get(requestMappingPath);

        if (controller != null) {

            HandlerMethodInfo handlerMethodInfo = handleMethodInfoMapping.get(requestMappingPath);

            try {
                if (handlerMethodInfo != null) {

                    String httpMethod = request.getMethod();

                    if (!handlerMethodInfo.getSupportedHttpMethods().contains(httpMethod)) {
                        // HTTP 方法不支持
                        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                        return;
                    }

                    if (controller instanceof PageController) {
                        pageControllerHandle(controller,request, response);
                        return;
                    } else if (controller instanceof RestController) {
                        restControllerHandle(controller,request, response,handlerMethodInfo);
                    }

                }
            } catch (Throwable throwable) {
                if (throwable.getCause() instanceof IOException) {
                    throw (IOException) throwable.getCause();
                } else {
                    throw new ServletException(throwable.getCause());
                }
            }
        }
    }

    /**
     * <P>RestController实现类处理器</P>
     * <ul><li>能对参数进行校验，对象绑定</li>
     * <li>能处理返回jsp，字符串，对象等数据</li></ul>
     * @param controller
     * @param request
     * @param response
     * @param handlerMethodInfo 方法对象
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IOException
     */
    private void restControllerHandle(Controller controller, HttpServletRequest request, HttpServletResponse response
            ,HandlerMethodInfo handlerMethodInfo) throws InvocationTargetException, IllegalAccessException, IOException, ServletException {

        //绑定对象参数 基本类型和json类型行不行？
        //这里有bug,如果给字段加了set方法，会报错
        Class<?>[] parameterTypes = handlerMethodInfo.getHandlerMethod().getParameterTypes();
        Object[] parameters = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Class paramType = parameterTypes[i];
            if(paramType.equals(HttpServletRequest.class)){
                parameters[i] = request;
            }else if(paramType.equals(HttpServletResponse.class)){
                parameters[i] = response;
            }else{
                parameters[i] = convertRequestParamsToEntity(request.getParameterMap(),paramType);
            }
        }
        //校验Bean
        Map<String,String> error = validateBean(handlerMethodInfo.getHandlerMethod(),parameters);
        request.setAttribute("error",error);

        Object resoponse = handlerMethodInfo.getHandlerMethod().invoke(controller, parameters);
        if(resoponse instanceof String){
            String res = String.class.cast(resoponse);
            if(res.endsWith(".jsp")){
                //转发 这里会把controller类上面的Path的路径带上
//                RequestDispatcher requestDispatcher = request.getRequestDispatcher(res);
                if (!res.startsWith("/")) {
                    res = "/" + res;
                }
                RequestDispatcher requestDispatcher = request.getServletContext().getRequestDispatcher(res);
                requestDispatcher.forward(request, response);
            }else{
                response.getWriter().write(res);
            }
        }else{
            response.getWriter().write(JSON.toJSONString(resoponse));
        }

    }

    /**
     * 校验Bean
     * 校验器从ioc中获取
     * @param handlerMethod
     * @param parameters
     * @return
     */
    private Map<String, String> validateBean(Method handlerMethod, Object[] parameters) {
        Map<String,String> error = new HashMap<>();
        Annotation[][] parameterAnnotations = handlerMethod.getParameterAnnotations();
        DelegatingValidator validator =(DelegatingValidator) this.parentContainer.getObject(ValidatorBean);

        for (int i = 0; i < parameters.length; i++) {
            Annotation[] parameterAnnotation = parameterAnnotations[i];
            boolean isValidate = false;
            for (Annotation annotation : parameterAnnotation) {
                if(Valid.class.isAssignableFrom(annotation.getClass())){
                    isValidate = true;
                    break;
                }
            }
            if(isValidate){
                error.putAll(validator.validate(parameters[i]));
            }
        }
        return error;
    }

    /**
     * 将请求内容转为对象
     * @param parameterMap
     * @param paramType
     * @return
     */
    private Object convertRequestParamsToEntity(Map<String, String[]> parameterMap, Class paramType) {
        try{
            if(paramType.isArray() || Collection.class.isAssignableFrom(paramType)
                || Map.class.isAssignableFrom(paramType) ){
                //TODO 类型是数组，集合，Map
            }else{
                Object obj = paramType.newInstance();
                BeanInfo beanInfo = Introspector.getBeanInfo(paramType, Object.class);
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                    String name = propertyDescriptor.getName();
                    String[] values = parameterMap.get(name);
                    //TODO 数据是数组，集合
                    if(values != null && values.length > 0){
                        propertyDescriptor.getWriteMethod().invoke(obj,values[0]);
                    }
                }
                return obj;
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }
        return null;
    }

    /**
     * PageController页面跳转的处理器
     * @param controller 目标类
     * @param request 请求
     * @param response 返回
     * @throws Throwable 异常
     */
    private void pageControllerHandle(Controller controller, HttpServletRequest request, HttpServletResponse response) throws Throwable {
        PageController pageController = PageController.class.cast(controller);
        String viewPath = pageController.execute(request, response);
        // 页面请求 forward
        // request -> RequestDispatcher forward
        // RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
        // ServletContext -> RequestDispatcher forward
        // ServletContext -> RequestDispatcher 必须以 "/" 开头
        ServletContext servletContext = request.getServletContext();
        if (!viewPath.startsWith("/")) {
            viewPath = "/" + viewPath;
        }
        RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(viewPath);
        requestDispatcher.forward(request, response);
    }


//    private void beforeInvoke(Method handleMethod, HttpServletRequest request, HttpServletResponse response) {
//
//        CacheControl cacheControl = handleMethod.getAnnotation(CacheControl.class);
//
//        Map<String, List<String>> headers = new LinkedHashMap<>();
//
//        if (cacheControl != null) {
//            CacheControlHeaderWriter writer = new CacheControlHeaderWriter();
//            writer.write(headers, cacheControl.value());
//        }
//    }
}
